/*
 * Copyright 2011-2012 Redia A/S
 * 
 * This file is part of Indhold+ for Android.
 *
 *  Redia Library App for Android is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Redia Library App for Android is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with Redia Library App for Android.  If not, see <http://www.gnu.org/licenses/>.
 */
package dk.redia.digiform.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.hoho.android.usbserial.driver.UsbSerialDriver;

import android.bluetooth.BluetoothSocket;
import android.util.Log;

import dk.redia.digiform.utils.ScanResultListener;


public class usbBasedRFIDScannerThread extends Thread {
	
	private UsbSerialDriver driver;
	private boolean running;
	private String currentTagId;
	public static final String logTag = "usbBasedRFIDScannerThread";
	
	private Set<ScanResultListener> listeners;
	
	public usbBasedRFIDScannerThread(UsbSerialDriver driver) throws IOException {
		setName("APSX RW-310 RFID Scanner");
		this.driver = driver;
		listeners = new HashSet<ScanResultListener>();
		if (driver==null) {
			throw new IOException("null driver instance provided to " + logTag);
		} else {
			this.driver.open();
			this.driver.setParameters(19200, UsbSerialDriver.DATABITS_8, UsbSerialDriver.STOPBITS_1, UsbSerialDriver.PARITY_NONE);
		}
	}
	
	public void addListener(ScanResultListener listener) {
		listeners.add(listener);
	}
	
	public void removeListener(ScanResultListener listener) {
		listeners.remove(listener);
	}

	public final int Iso15693Crc(byte buffer[])
	{
		int reg;
		int j;
		
		reg = 0xffff;
	    for (int i = 0; i < buffer.length ; i++) {
		reg = reg ^ (buffer[i]);
			for (j = 0; j < 8; j++) {
				if ((reg & 0x0001) != 0) {
					reg = (reg >> 1) ^ 0x8408;
				} else {
					reg = (reg >> 1);
				}
			}
		}

		return ~((reg & 0xffff)-65536);
	}
	
	private int sendCommand(byte txBuffer[])
	{
	    // First two bytes in the command is not included in the CRC
	    // so we make a copy for CRC indexed from third byte 
	    byte[] crc = new byte[txBuffer.length-2];
	    System.arraycopy(txBuffer, 2, crc, 0, crc.length);
	    int crc16 = Iso15693Crc(crc);
	    
	    byte[] wrappedCommand = new byte[txBuffer.length + 2]; // Make room for CRC16
	    System.arraycopy(txBuffer, 0, wrappedCommand, 0, txBuffer.length);
	    
	    // Append the 16 bit CRC
	    wrappedCommand[wrappedCommand.length-2] = (byte) (crc16 & 0xff);
	    wrappedCommand[wrappedCommand.length-1] = (byte) ((crc16 >> 8) & 0xff);
	    
	    int didSend = -1;

	    try {
			didSend = driver.write(wrappedCommand, 1000);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	    // TODO change from wait to actually count the bytes received, and start parsing when the correct amount of date is received
	    try {
	        Thread.sleep(50);
	    } catch(InterruptedException ex) {
	        Thread.currentThread().interrupt();
	    }

	    return didSend;
	}
		
	private String getTagId()
	{
	    // Read block command including package size
		byte txBuffer[] = new byte[]{0x06, 0x0c, 0x27, 0x01, 0x00};
		
		sendCommand(txBuffer);
		
	    byte rxBuffer[] = new byte[16];

	    int numBytesRead = -1;
	    
	    // TODO chech for amount of received bytes
	    try {
			numBytesRead = driver.read(rxBuffer, 1000);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    
	    
	    if (numBytesRead<10) return "";
	    
	    byte crcBuffer[] = new byte[numBytesRead-3];

	    System.arraycopy(rxBuffer, 0, crcBuffer, 0, numBytesRead-3);
	    
	    int calculatedCRC = Iso15693Crc(crcBuffer);
	    int expectedCRC   = ((rxBuffer[11] << 8) & 0xff00) | (rxBuffer[10] & 0xff);
	    
	    // TODO The CRC does not match
	    
	    // First byte is errorcode or first byte in ID (I guess)
	    if (rxBuffer[0]!=80) {
	    	// Data is valid, so we copy them to buffer
		    byte tagBuffer[] = new byte[12];
		    System.arraycopy(rxBuffer, 0, tagBuffer, 0, 12);

	    	StringBuilder sb = new StringBuilder();
	        for (byte b : tagBuffer) {
	        	sb.append(String.format("%02X:", b));
	        }
	        return sb.toString();
	    }
	    
		return "";
	}
	
	private int getBlock(byte block, final byte data[])
	{
	    // Read block command including package size
		byte txBuffer[] = new byte[]{0x06, 0x07, 0x03, 0x20, block};
		
		sendCommand(txBuffer);
		
	    byte rxBuffer[] = new byte[16];

	    int numBytesRead = -1;
	    
	    try {
			numBytesRead = driver.read(rxBuffer, 1000);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	    byte crcBuffer[] = new byte[5];

	    System.arraycopy(rxBuffer, 0, crcBuffer, 0, 5);
	    
	    int calculatedCRC = Iso15693Crc(crcBuffer);
	    int expectedCRC   = ((rxBuffer[6] << 8) & 0xff00) | (rxBuffer[5] & 0xff);
	    
	    // TODO Fix this ugly hack
	    if (calculatedCRC != expectedCRC) {
	    	numBytesRead = -1;
	    } else {
	    	// Data is valid, so we copy them to buffer
	    	System.arraycopy(rxBuffer, 1, data, 0, 4);
	    }
	    
		return numBytesRead;
	}
		
	public void run() {
		running = true;
		
 		while (running) {
			    // Read command including package size
//				byte txBuffer[] = new byte[]{0x06, 0x0c, 0x27, 0x01, 0x00};

			    // Read block command including package size
//				byte txBuffer[] = new byte[]{0x06, 0x07, 0x03, 0x20, 0x00};
			    
//				int didSend = sendCommand(txBuffer);

 				String tid = getTagId();
 				if (tid!="")  notifyListenersLog(logTag, "Found tag with id " + tid);

				// Skip scan result if we found the same tag id during our last
				// scan
				if (currentTagId!=null && currentTagId.equals(tid)) {
					notifyListenersLog(logTag, "We already found this tag, so no need to announce it again");
					continue;
				}
				
				int fetchAmountOfBlocks = 4;
				byte block[] = new byte[16];
			    byte payload[] = new byte[fetchAmountOfBlocks*4];
	
			    int actualNumberOfReceivedBlocks = 0;
			    for (int i = 0; i < fetchAmountOfBlocks; i++)
			    {
				    for (int t = 0; t < 3; t++) {
				    	
				    	int result = getBlock((byte) i, block);
					    if (result != -1) {
						    System.arraycopy(block, 0, payload, 4*i, 4);
						    actualNumberOfReceivedBlocks++;
						    break;
					    }
				    }
			    }
			    
//			    Log.d(logTag, "actuallyReceivedBlocks: " + actuallyReceivedBlocks);
			    if (actualNumberOfReceivedBlocks != fetchAmountOfBlocks) {
               		continue;
			    }
               	
            	String data = new String(payload, Charset.forName("US-ASCII"));
            	Pattern p = Pattern.compile("([0-9]+)");
            	Matcher matcher = p.matcher(data);
				String resourceId = null;
            	if (matcher.find()) {
            			resourceId = matcher.group(0);
               	} else {
					notifyListenersLog(logTag, "Unable to parse RFID payload: " + data);
					continue;
               	}

				currentTagId = tid;
//    			Log.d(logTag, "resourceId: " + resourceId);
				notifyListenersScanned(resourceId);
               	
               	
/*				
			    byte buffer[] = new byte[16];
			    int numBytesRead = driver.read(buffer, 1000);
			    String str = Arrays.toString(buffer);

			    Log.d(logTag, "Bytes received: " + numBytesRead + " bytes.");
			    Log.d(logTag, "Data: " + str);
*/
/*			    
				try {
					Thread.sleep(250);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
*/				
/*
				//notifyListenersLog(logTag, "Wrote SCAN command to RFID scanner");				
				String response = readResponse(is);
				if (response==null) {
					notifyListenersDisconnected();
					return;
				}
				if (response.length()<4) {
					// Remember to unset the current tag id
					currentTagId = null;
					continue;
				}
				// Remove initial 4 characters, which contain a response code
				// Remaining characters contain the tag id
				String tid = response.substring(4);
				notifyListenersLog(logTag, "Found tag with id " + tid);
				// Skip scan result if we found the same tag id during our last
				// scan
				if (currentTagId!=null && currentTagId.equals(tid)) {
					notifyListenersLog(logTag, "We already found this tag, so no need to announce it again");
					continue;
				}
				
				// Write command to retrieve payload data from the "Smart Label"
				String cmd = "\r402401" + tid + "0010\r"; 
				os.write(cmd.getBytes());
				os.flush();
				try {
					Thread.sleep(250);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				String data = readResponse(is);
				if (data.length()<4) {
					notifyListenersLog(logTag, "Failed to read data from RFID tag");
					continue;
				}
				// Strip first 2 characters, as they contain a response code
				
				data = data.substring(2);
				// Decode payload
				data = fromHex(data);
			//	notifyListenersLog(logTag,"scanner got data: "+ data);
				Pattern p = Pattern.compile("([0-9]*)[a-zA-Z]?DK.*");
				Matcher m = p.matcher(data);
				String resourceId = null;
				if (m.matches()) {
					resourceId = m.group(1);
				} else {
					notifyListenersLog(logTag, "Unable to parse RFID payload: " + data);
					continue;
				}
				
			//	notifyListenersLog(logTag, "resourceID: "+resourceId);
				// We are sure to have successfully read valid data from the tag
				// so make a note that we did, and avoid reading it again and again.
				currentTagId = tid;
				notifyListenersScanned(resourceId);
//				return fromHex(data);
			*/
/*
 		} catch (IOException e) {
				Log.d(logTag, "IOException during communication with scanner: " + e.getMessage());
				notifyListenersDisconnected();
				return;
			}
*/
		}
	}
		
	private String readResponse(InputStream is) {
		byte previous = '\0';
		byte[] buffer = new byte[1];
		int bytesRead;
		String result = new String();
		//boolean responseRead = false;
		try {
			//int bytesRead = 0;
			//if(is.available() > 0){
			while ((bytesRead = is.read(buffer, 0, buffer.length))>=0) {
				//uiLog(logTag, "Read " + bytesRead + " bytes");
				if (buffer[0]=='\n' && previous=='\r') {
					return result;
				}
				if (buffer[0]!='\n' && buffer[0]!='\r') {
					result += (char)buffer[0];
				}
				previous = buffer[0];
			}
			//}
			return result;
			
		} catch (IOException e) {
			notifyListenersLog(logTag, "Error in readResponse: "+e.getMessage());
			e.printStackTrace();
			notifyListenersDisconnected();
			return null;
		}
		
	}
	
	private void notifyListenersDisconnected() {
		for (ScanResultListener listener : listeners) {
			listener.disconnected();
		}
	}
	
	private void notifyListenersScanned(String resourceId) {
		for (ScanResultListener listener : listeners) {
			listener.scanned(resourceId);
		}		
	}
	
	private void notifyListenersLog(String logTag, String msg) {
		for (ScanResultListener listener : listeners) {
			listener.log(msg);
		}	
	}
}
